/**
 * 2023/3/16开始
 * 
 */
import compileToFunction from './compile/index.js';
import { initGlobalApi } from './global-api/index.js';
import { mergeOptions } from './utils/index.js';
import { renderMixin } from './vnode/index.js';
import { callHook, lifecycleMixin, mouentComponent } from './vnode/lifecycle.js';

// 公共方法
function judgeDataType(val){
  return Object.prototype.toString.call(val);
}

// 重写数组方法
const oldArrayPrototype = Array.prototype;
// 继承数组原型
const ArrayMethods = Object.create(oldArrayPrototype);
// 需要被重写的方法
let methods = [
  'push',
  'pop',
  'shift',
  'unshift',
  'splice'
]
methods.forEach(item => {
  ArrayMethods[item] = function(...args){ // 重写方法
    const result = oldArrayPrototype[item].apply(this, args); // 执行方法用于返回数据
    // 添加对操作数组方法传入的值的劫持
    let inserted; // 往数组添加的数据
    switch (item) {
      case 'push':
      case 'unshift':
        inserted = args;
        break;
      case 'splice':
        inserted.splice(2); // 操作splice往数组添加数据时从第三项开始才是要插入的数据
        break;
    }
    this.__ob__.observeArray(inserted);
    return result;
  }
});

// 观测数据
function observe(data){
  if(typeof data !== 'object'){ return; }
  return new Observe(data);
}
class Observe{
  constructor(data){
    Object.defineProperty(data, '__ob__', { // 添加__ob__给对象劫持的时候使用
      enumerable: false,
      value: this // 指向当前observe函数
    })
    // vue2重写了数组的七个方法 所以此时对数组类型的数据需要区分观察
    if(Array.isArray(data)){
      data.__proto__ = ArrayMethods; // 覆盖原本的数组原型
      // 数组内包含对象
      this.observeArray(data);
    } else {
      this.walk(data)
    }
  }
  // 遍历每一个属性
  walk(data){
    for (const key in data) {
      if (Object.hasOwnProperty.call(data, key)) {
        const element = data[key];
        defineReactive(data, key, element);
      }
    }
  }
  observeArray(data){
    data.forEach(item => {
      observe(item); // 观察数组中的对象 给数组中的对象添加响应式
    })
  }
}
// 添加数据劫持
function defineReactive(data, key, value){
  observe(value); // 深度代理(劫持)
  Object.defineProperty(data, key, {
    get(){
      return value;
    },
    set(newValue){
      /**
       * 当直接赋值一个属性为对象时 新的对象没有被代理 所以在这再次观测一次
       * 例如：data中存在obj: { a: 1 }  如果将obj = { b: 1 } 此时b将没有被观测到
       */
      if(newValue === value) return;
      observe(newValue);
      value = newValue;
    }
  })
}

class Vue{
  constructor(options){
    this._init(options)
  }
  _init(options){ // 初始化数据
    this.$options = mergeOptions(Vue.options, options);
    this._initState(options); // 初始化data数据

    options.el && this.$mount(options); // 编写挂载方法
    
  }
  _initState(options){
    if(options.data){
      callHook(this, 'beforeCreate') // 此处执行生命周期函数beforeCreate
      this._initData(options.data)
      callHook(this, 'created') // 此处执行生命周期函数created
    }
  }
  _initData(data){
    /**
     * typeof可以直接判断是否是函数 所以不需要其它判断方法
     * data可以接受两种类型数据 函数和对象 并且函数必须返回一个对象
     * 给vue实例上添加一个_data熟悉，可以通过vue实例访问数据
     */
    data = this._data = typeof data === 'function' ? data.call(this) : data;
    // 验证数据类型
    if(judgeDataType(data) !== '[object Object]'){
      throw 'data 必须传入一个对象或者传入一个返回一个对象的函数'
    }

    // 对_data属性进行代理 使可以直接通过vue实例访问到data中声明的遍历
    for (const key in data) {
      if (Object.hasOwnProperty.call(data, key)) {
        proxy(this, '_data', key);
      }
    }

    // 开始开启观察者 (观测数据)
    observe(data);
  }
  // 挂载方法
  $mount(options){
    let el = document.querySelector(options.el);
    this.$el = el;
    if(!options.render){ // 没有render方法
      let template = options.template; // 获取template
      if(!template && options.el){ // 没有template但是有el
        el = el.outerHTML;
        // 把dom转变为ast语法树
        let render = compileToFunction(el);
        this.$options.render = render;
      }
    }
    mouentComponent(this, el)
  }
}
lifecycleMixin(Vue); // 添加生命周期
renderMixin(Vue); // 添加渲染函数
initGlobalApi(Vue);

// 相当于把_data中的变量全部放入vue实例中 但是引用的值是同一个地址
function proxy(target, source, key){
  Object.defineProperty(target, key, {
    get(){
      return target[source][key];
    },
    set(value){
      target[source][key] = value;
    }
  })
}

export default Vue;